/*DrawingServant Version 0.1

A utility program for putting a NeXT graphics head on a
terminal based program.  Since this is never intended to be
a REAL NeXT application, many parts of an "official" application
(i.e. Preferences, Help, language selection...) are omitted.
This program is primarily intended to be forked from another process
and communicated to through pipes. (Although you can invoke it
directly and have yet another PS hacker's treat.  Perhaps it is
slightly easier to use than pft.)
Comments, additions, subtractions are appreciated
	joe@ril3.tamri.com
Copyright 1993 Joe Carlson
Permission is granted for non-commercial reproduction and
redistribution of this code provided this copyright notice
remains.
If you want to port some common program with this, drop me
a note.  I'll try to keep track of what is out there.

This is IB free.  The code is maybe slightly more amenable to hacking
because of it.
*/

#import <appkit/appkit.h>
#import "DrawingServant.h"

/* Default values for in and out pipes, x and y location
and height and width of the window.  Last number is initial
size of PS code */
#define DEFINPIPE 0
#define DEFOUTPIPE 1
#define DEFX 400.
#define DEFY 250.
#define DEFH 500.
#define DEFW 500.
#define MAXSTRINGSSTART 512
static char *VERSIONID = "Drawing Servant Version 0.1\n";
static char *ACKSTRING = "Ok\n";

/* things for possible improvements and additions:
	1) multiple windows
	2) error handling
	3) why do I need the gsave/grestore?
*/

id DrawView;
id DrawWindow;
id myWindow, myMenu, windowText;
id Panelview,myPanel;

void setUp(argc,argv)
int argc;
char **argv;
{


	NXRect aRect;
	int i;

	void *data;
	void iohandler();

	/* Set up a Panel */

	NXSetRect(&aRect, 150.0, 600.0, 300.0, 200.0);
	Panelview = [[InfoPanel alloc]init];
	[Panelview setOpaque:YES];
	myPanel = [[Panel alloc] initContent:&aRect
					style:NX_TITLEDSTYLE
					backing:NX_BUFFERED
					buttonMask:NX_CLOSEBUTTONMASK
				defer:YES];
	[myPanel setTitle:"About Drawing Servant"];
	[myPanel setContentView:Panelview];

	/* set up the drawing window */
	DrawView = [[Drawclass alloc] init];
	[DrawView commandlineargs:argc:argv];
	[DrawView setOpaque:YES];
	DrawWindow = [[Window alloc]initContent:[DrawView viewbox:nil]
					style:NX_TITLEDSTYLE
					backing:NX_BUFFERED
					buttonMask:NX_CLOSEBUTTONMASK
					defer:NO];

	[DrawWindow setTitle:"Drawing Servant"];
	[DrawWindow setContentView:DrawView];
	[DrawWindow orderFront:nil];
	[DrawWindow display];
	[DrawView mouseoff:nil];
	[DrawView ackon:nil];
	[DrawView expandstringarea:MAXSTRINGSSTART From:0];
	[[DrawView window]addToEventMask: NX_RMOUSEDOWNMASK];


	/* Set up a Menu */
	myMenu = [[Menu alloc] initTitle:"Drawing Servant"];
	[[myMenu addItem:"Info..."
			action:@selector(orderFront:)
			keyEquivalent:'\0']
				setTarget:myPanel];
	/* gee...I'd like to get this working next... */
	/*[[myMenu addItem:"New"
			action:@selector(newview:)
			keyEquivalent:'n']
			setTarget:DrawView];*/
	[[myMenu addItem:"Clear"
			action:@selector(clearview:)
			keyEquivalent:'c']
			setTarget:DrawView];
	[[myMenu addItem:"Save"
			action:@selector(saveview:)
			keyEquivalent:'s']
			setTarget:DrawView];
	[[myMenu addItem:"Print"
			action:@selector(printview:)
			keyEquivalent:'p']
			setTarget:DrawView];
	[myMenu addItem:"Hide"
			action:@selector(hide:)
			keyEquivalent:'h'];
	[myMenu addItem:"Quit"
			action:@selector(terminate:)
			keyEquivalent:'q'];
	[myMenu sizeToFit];
	[NXApp setMainMenu:myMenu];


	/* prepare to interrupt on input */
	DPSAddFD([DrawView inpipe:nil],iohandler,data,NX_BASETHRESHOLD);
	
	return;
}
void
iohandler(fd,data)
int fd;
void *data;
{

	char string[BUFSIZ];
	int i;
	void processcommand();

	read([DrawView inpipe:nil],string,sizeof(string)-1);

	/* just make sure we are terminated */
	for(i=0;i<strlen(string);i++) {
		if(string[i]=='\n' || string[i]==0){
			string[i]='\n';
			/* zero out the rest, just to be safe... */
			for(i++;i<strlen(string);string[i++]=0);
			break;
		}
	}

	processcommand(string);
}

processcommand(string)
char *string;
{
	if(!strncmp(string,"DSversion",9) ) {
		write([DrawView outpipe:nil],VERSIONID,strlen(VERSIONID));

	} else if (!strncmp(string,"DSclear",7) ) {
		[DrawView clearview:nil];

	} else if (!strncmp(string,"DSsave",6) ) {
		[DrawView saveview:nil];

	} else if (!strncmp(string,"DSoops",6) ) {
		[DrawView subfromstrings:string];

	} else if (!strncmp(string,"DSprint",7) ) {
		[DrawView printview:nil];

	} else if (!strncmp(string,"DSmouseon",9) ) {
		[DrawView mouseon:nil];

	} else if (!strncmp(string,"DSmouseoff",10) ) {
		[DrawView mouseoff:nil];

	} else if (!strncmp(string,"DSackon",7) ) {
		[DrawView ackon:nil];

	} else if (!strncmp(string,"DSackoff",8) ) {
		[DrawView ackoff:nil];

	} else if (!strncmp(string,"DSfront",7) ) {
		[[DrawView window] orderFrontRegardless];

	} else if (!strncmp(string,"DShide",6) ) {
		[NXApp hide:nil];

	} else if (!strncmp(string,"DSquit",6) ) {
		[NXApp terminate:nil];

	} else {
		[DrawView addtostrings:string];
	}
	/* 'S alright? */
	if([DrawView isackon:nil])
	write([DrawView outpipe:nil],ACKSTRING,strlen(ACKSTRING));

}

@implementation Drawclass
-drawSelf: (NXRect *)rects: (int)rectCount;
{
	int i;
	
	/* just clear the screen */
	if(needtoclear) {
		PSsetgray(NX_WHITE);
		PSrectfill(bounds.origin.x,bounds.origin.y,
				bounds.size.width,bounds.size.height);
		PSsetgray(NX_BLACK);
		needtoclear = NO;
	/* just write the last string to the window server */
	} else if(justlast) {
		if(nstrings) {
		/* can someone tell me why the save/restore is needed?*/
			PSgrestore();
			DPSPrintf(DPSGetCurrentContext(),
					"%s \n",psstrings[nstrings-1]);
			PSgsave();
		}
	/* the whole thing */
	} else {
		for(i=0;i<nstrings;) {
			DPSPrintf(DPSGetCurrentContext(),"%s \n",psstrings[i++]);
		}
	}
	return self;
}
-addtostrings:(char *)string;
{
	/* if we got too many strings, allocate space for more */
	if(nstrings==maxstrings)
		[self expandstringarea:2*maxstrings From:maxstrings];

	psstrings[nstrings] = malloc(strlen(string)+1);
	strcpy(psstrings[nstrings++],string);

	/* when the string is added to the list, we just need to
	update the screen with the last line */
	justlast = YES;
	[self display];

	/* otherwise (for printing or saving) we'll want to send to
	entire list of postscript commands */
	justlast = NO;
	return;
}
-subfromstrings:(char *)string;
{
	/* remove the last */
	if(nstrings)
		free(psstrings[--nstrings]);
	/*redraw all the ps code */
	justlast = NO;
	[self display];
}
-clearview:sender;
{
	for(;nstrings;) {
		free(psstrings[--nstrings]);
	}
	needtoclear = YES;
	[self display];
	return self;
}
-saveview:sender;
{
	id save;
	save = [[SavePanel new]
			setRequiredFileType:"eps"];

	if ([save runModal]==1) {
		NXStream *aStream;
		aStream = NXOpenMemory(0,0,NX_WRITEONLY);
		[self copyPSToStream:aStream forView:self];
		NXSaveToFile(aStream, [save filename]);
		NXCloseMemory(aStream, NX_FREEBUFFER);
	}
	return self;
}
-copyPSToStream:(NXStream *)aStream forView:view;
{
	NXRect boundingbox;

	[view getBounds:&boundingbox];
	[view copyPSCodeInside:&boundingbox to:aStream];
	return self;
}	
-printview:sender
{
	[self printPSCode:nil];
	return self;
}	
-(int)inpipe:sender
{
	return inpipenumber;
}
-(int)outpipe:sender
{
	return outpipenumber;
}
-(NXRect *)viewbox:sender
{
	return &windowrectangle;
}
-expandstringarea:(int)to From:(int)from
{
	char **temp;
	int i;

	temp = (char **) malloc(to*sizeof(char *));
	for(i=0;i<from;i++) {
		temp[i] = psstrings[i];
	}
	if(from)
		free(psstrings);
	psstrings = temp;
	maxstrings = to;
	return;
}
-mouseon:sender
{
	printmouseclicks = YES;
	return self;
}
-mouseoff:sender
{
	printmouseclicks = NO;
	return self;
}
-mouseDown:(NXEvent *)theEvent
{
	char string[80];

	if(printmouseclicks) {
		sprintf(string,"left mouse %f %f\n",
			theEvent->location.x,theEvent->location.y);
		write([DrawView outpipe:nil],string,strlen(string));
	}
	return;

return self;
}
-rightMouseDown:(NXEvent *)theEvent
{
	char string[80];

	if(printmouseclicks) {
		sprintf(string,"right mouse %f %f\n",
			theEvent->location.x,theEvent->location.y);
		write([DrawView outpipe:nil],string,strlen(string));
	}
	return;

return self;
}

-ackon:sender
{
	sendackstring = YES;
	return self;
}
-ackoff:sender
{
	sendackstring = NO;
	return self;
}
-(BOOL)isackon:sender
{
	if(sendackstring)
		return YES;
	else
		return NO;
}
-commandlineargs: (int)argc: (char **)argv;
/* process the command line arguments. Nothing fancy here, just
plow through things.  - signs before args are not required.*/
{
	int i;

	/* reasonable guesses for things like in and outpipes,
	height width and placement */
	height = DEFH;
	width = DEFW;
	xoffset = DEFX;
	yoffset = DEFY;
	inpipenumber = DEFINPIPE;
	outpipenumber = DEFOUTPIPE;
	for(i=1;i<argc;) {
		/* strip off tabs and blanks */
		for(;argv[i][0]==' ' || argv[i][0]=='	';)
			strcpy(argv[i],argv[i]+1);
		/* perhaps arguments have a - in front; or maybe not.
		we are processing everything as a switch, strip off the - */
		if(argv[i][0]=='-') 
			strcpy(argv[i],argv[i]+1);
		switch (argv[i][0]) {
			case 'h': case 'H':
				if(argv[i][1]==0){
					if(++i==argc) 
						break;
					height = atof(argv[i]);
				} else {
					height = atof(argv[i]+1);
				}
				i++;
				break;
			case 'w': case 'W':
				if(argv[i][1]==0) {
					if(++i==argc)
						break;
					width = atof(argv[i]);
				} else {
					width = atof(argv[i]+1);
				}
				i++;
				break;
			case 'x': case 'X':
				if(argv[i][1]==0) {
					if(++i==argc)
						break;
					xoffset = atof(argv[i]);
				} else {
					xoffset = atof(argv[i]+1);
				}
				i++;
				break;
			case 'y': case 'Y':
				if(argv[i][1]==0) {
					if(++i==argc)
						break;
					yoffset = atof(argv[i]);
				} else {
					yoffset = atof(argv[i]+1);
				}
				i++;
				break;
			case 'i': case 'I':
				if(argv[i][1]==0) {
					if(++i==argc)
						break;
					inpipenumber=atoi(argv[i]);
				} else {
					inpipenumber=atoi(argv[i]+1);
				}
				i++;
				break;
			case 'o': case 'O':
				if(argv[i][1]==0) {
					if(++i==argc)
						break;
					outpipenumber=atoi(argv[i]);
				} else {
					outpipenumber=atoi(argv[i]+1);
				}
				i++;
				break;
			default:
				i++;
			}
		}

	NXSetRect([self viewbox:nil],xoffset,yoffset,width,height);
}
@end

main(argc,argv)
int argc;
char **argv;
{
	[Application new];
	setUp(argc,argv);
	[NXApp run];
	[NXApp free];
}
@implementation InfoPanel
-drawSelf: (NXRect *)rects: (int) rectCount;
{
	NXRect aRect;
	DPSTimedEntryProc melter();

	NXSetRect(&aRect,0.,0., 300.0, 200.0);
	PSsetgray(NX_WHITE);
	PSrectfill(bounds.origin.x,bounds.origin.y,
				bounds.size.width,bounds.size.height);
	PSsetgray(NX_BLACK);
	PSmoveto(45.,150.);
	PSselectfont("Times-Roman",20.);
	PSshow(VERSIONID);

	PSselectfont("Times-Roman",12.);
	PSmoveto(45.,100.);
	PSshow("Copyright 1993 by J Carlson");
	PSmoveto(45.,85.);
	PSshow("Permission is granted for noncommercial");
	PSmoveto(45.,70.);
	PSshow("Redistribution of this program");
	PSmoveto(45.,55.);
	PSshow("Provided this copyright notice stays intact.");

	return self;
}
@end
